// tge.rs
// copyright zipxing@hotmail.com 2022~2023

//! 实现了加载和保存.sdl .esc文件的方法
//!
//! esc文件存储终端模式下的字符图，存储esc终端控制序列
//! 和utf8文本，可以在终端下直接用cat查看效果:
//!
//! $ cat assets/tetris/back.txt
//!
//! sdl文件存储SDL模式下的字符图，按行存储cell序列，
//! cell: 字符sym索引，前景色，背景色（SDL模式下背景色标识纹理）
//!
//! $ cat assets/snake/back.sdl
//!

use crate::{
    render::buffer::Buffer,
    render::adapter::sdl::sdlsym,
    render::style::{Color, Style},
    util::{get_abs_path, Point},
};
use log::info;
use regex::Regex;
use std::{
    fs::File,
    io::{self, BufRead, BufReader, Error, Write},
};
use unicode_width::UnicodeWidthStr;

/// 转换错误的一个辅助函数
pub fn to_error(error: crossterm::Result<()>) -> Result<(), String> {
    error.map_err(|e| e.to_string())
}

/// 转换错误的一个辅助函数
pub fn io_error(error: crossterm::Result<()>) -> io::Result<()> {
    error.map_err(|e| io::Error::new(io::ErrorKind::Other, e.to_string()))
}

#[derive(Debug)]
pub enum DatFileError {
    Io(Error),
}

impl From<Error> for DatFileError {
    fn from(err: Error) -> DatFileError {
        DatFileError::Io(err)
    }
}

fn find_vaild_area(content: &Buffer) -> (u16, u16, u16, u16) {
    let width = content.area.width;
    let rowcnt = content.area.height;
    //裁剪有效数据，去除空白
    let (mut x1, mut y1, mut x2, mut y2) = (u16::MAX, u16::MAX, 0, 0);
    for row in 0..rowcnt {
        for col in 0..width {
            let cell = &content.content[(row * width + col) as usize];
            if !cell.is_blank() {
                if col < x1 {
                    x1 = col;
                }
                if col > x2 {
                    x2 = col;
                }
                if row < y1 {
                    y1 = row;
                }
                if row > y2 {
                    y2 = row;
                }
            }
        }
    }
    info!("save_sdlfile:: x1{} x2{} y1{} y2{}", x1, x2, y1, y2);
    (x1, x2, y1, y2)
}

#[cfg(feature = "sdl")]
pub fn save_sdlfile(content: &Buffer, fpath: &str) -> Result<(), DatFileError> {
    let fpstr = get_abs_path(fpath);
    let mut outf = File::create(fpstr)?;
    let (x1, x2, y1, y2) = find_vaild_area(content);
    let width = content.area.width;
    writeln!(
        &mut outf,
        "width={},height={},texture={}",
        x2 - x1 + 1,
        y2 - y1 + 1,
        255
    )?;
    for row in y1..y2 + 1 {
        let line = &content.content[(row * width + x1) as usize..(row * width + x2 + 1) as usize];
        for (_i, cell) in line.iter().enumerate() {
            let (idx, _, _) = cell.get_sdl_info();
            write!(
                &mut outf,
                "{},{},{} ",
                idx,
                u8::from(cell.fg),
                u8::from(cell.bg)
            )?;
        }
        write!(&mut outf, "\n")?;
    }
    Ok(())
}

pub fn load_sdlfile(
    content: &mut Buffer,
    fpath: &str,
    off_x: u16,
    off_y: u16,
) -> Result<Point, DatFileError> {
    let fpstr = get_abs_path(fpath);
    let file = File::open(fpstr)?;
    let reader = BufReader::new(file);

    let re = Regex::new(r"width=(\d+),height=(\d+),texture=(\d+)").unwrap();
    let rel0 = Regex::new(r"(\d+),(\d+)(.*?)").unwrap();
    let rel1 = Regex::new(r"(\d+),(\d+),(\d+)(.*?)").unwrap();
    let mut width: u16 = 0;
    let mut height: u16 = 0;
    let mut texid: u8 = 0;
    let mut lineidx = 0;
    //info!("begin load_sdl....");
    for line in reader.lines() {
        let l = line?;
        //info!("load_sdl line={}", l);
        if lineidx == 0 {
            for cap in re.captures_iter(&l) {
                width = cap[1].parse::<u16>().unwrap();
                height = cap[2].parse::<u16>().unwrap();
                texid = cap[3].parse::<u8>().unwrap();
                info!("w..{} h..{} l..{}", width, height, texid);
            }
        } else {
            let mut col = 0;
            if texid < 255 {
                for cap in rel0.captures_iter(&l) {
                    let idx = cap[1].parse::<u8>().unwrap();
                    let fgc = cap[2].parse::<u8>().unwrap();
                    content.set_str(
                        col + off_x,
                        lineidx - 1 + off_y,
                        sdlsym(idx),
                        Style::default()
                            .fg(Color::Indexed(fgc))
                            .bg(Color::Indexed(texid)),
                    );
                    col += 1;
                }
            } else {
                for cap in rel1.captures_iter(&l) {
                    let idx = cap[1].parse::<u8>().unwrap();
                    let fgc = cap[2].parse::<u8>().unwrap();
                    let bgc = cap[3].parse::<u8>().unwrap();
                    content.set_str(
                        col + off_x,
                        lineidx - 1 + off_y,
                        sdlsym(idx),
                        Style::default()
                            .fg(Color::Indexed(fgc))
                            .bg(Color::Indexed(bgc)),
                    );
                    col += 1;
                }
            }
        }
        lineidx += 1;
    }
    Ok(Point {
        x: width,
        y: height,
    })
}

pub fn save_escfile(content: &Buffer, fpath: &str) -> Result<(), DatFileError> {
    let fpstr = get_abs_path(fpath);
    let mut outf = File::create(fpstr)?;
    let width = content.area.width;
    //let rowcnt = self.content.area.height;
    let (x1, x2, y1, y2) = find_vaild_area(content);
    for row in y1..y2 + 1 {
        let line = &content.content[(row * width + x1) as usize..(row * width + x2 + 1) as usize];
        let mut fg = Color::Reset;
        let mut bg = Color::Reset;
        let mut span = String::new();
        let mut skip = 0i8;
        for (_i, cell) in line.iter().enumerate() {
            info!(
                "save_esc symbol={} symwidth={}",
                cell.symbol,
                cell.symbol.width()
            );
            //对于中文等宽字符后面跟的空白，要略过处理
            if skip > 0 {
                info!("skip, skip={}", skip);
                skip -= 1;
                continue;
            }
            let sw = cell.symbol.width();
            if sw > 1 {
                skip = sw as i8;
                skip -= 1;
                info!("set skip, skip={}", skip);
            }
            if cell.fg != fg || cell.bg != bg {
                if span.len() != 0 {
                    if fg == Color::Reset && bg == Color::Reset {
                        outf.write_all(span.as_bytes())?;
                    } else {
                        let ss = format!(
                            "\x1b[38;5;{}m\x1b[48;5;{}m{}\x1b[0m",
                            u8::from(fg),
                            u8::from(bg),
                            span
                        );
                        outf.write_all(ss.as_bytes())?;
                    }
                    span.clear();
                }
                fg = cell.fg;
                bg = cell.bg;
                span.push_str(&cell.symbol);
            } else {
                span.push_str(&cell.symbol);
            }
        }
        if span.len() != 0 {
            if fg == Color::Reset && bg == Color::Reset {
                outf.write_all(span.as_bytes())?;
            } else {
                let ss = format!(
                    "\x1b[38;5;{}m\x1b[48;5;{}m{}\x1b[0m",
                    u8::from(fg),
                    u8::from(bg),
                    span
                );
                outf.write_all(ss.as_bytes())?;
            }
            span.clear();
        }
        outf.write_all("\n".as_bytes())?;
    }
    Ok(())
}

pub fn load_escfile(
    content: &mut Buffer,
    fpath: &str,
    off_x: u16,
    off_y: u16,
) -> Result<Point, DatFileError> {
    let fpstr = get_abs_path(fpath);
    let file = File::open(fpstr)?;
    let reader = BufReader::new(file);

    let mut row = 0;
    let mut max_width: u16 = 0;
    //info!("begin load_esc....");
    for line in reader.lines() {
        let l = line?;
        //info!("load_esc line={}", l);
        let lw = escstr_to_buffer(&l, content, row, off_x, off_y);
        if lw > max_width {
            max_width = lw;
        }
        row += 1;
    }
    Ok(Point {
        x: max_width,
        y: row,
    })
}

pub fn escstr_to_buffer(l: &String, content: &mut Buffer, row: u16, off_x: u16, off_y: u16) -> u16 {
    let mut pos = 0;
    let mut cell_pos = 0;
    let mut lpos = 0;
    let mut lcell_pos = 0;
    let re = Regex::new(r"\x1b\[38;5;(\d+)m\x1b\[48;5;(\d+)m(.*?)\x1b\[0m").unwrap();
    for cap in re.captures_iter(l) {
        let cr = cap.get(0).unwrap();
        //info!("load_esc set1 x={} str={}", cell_pos + off_x, &l[pos..cr.start()]);
        content.set_str(
            cell_pos + off_x,
            row + off_y,
            &l[pos..cr.start()],
            Style::default(),
        );
        //注意要使用unicode的长度，不能直接使用byte长度
        //例如♥的正确长度是1，而byte长度是3
        //let graphemes = UnicodeSegmentation::graphemes(&l[pos..cr.start()], true)
        //    .collect::<Vec<&str>>();
        //采用宽字符个数也不行
        //cell_pos += graphemes.len() as u16;
        //采用width返回真正的字符宽度,
        cell_pos += l[pos..cr.start()].width() as u16;
        //info!("load_esc set2 x={} str={}", cell_pos + off_x, &cap[3]);
        content.set_str(
            cell_pos + off_x,
            row + off_y,
            &cap[3],
            Style::default()
                .fg(Color::Indexed(cap[1].parse::<u8>().unwrap()))
                .bg(Color::Indexed(cap[2].parse::<u8>().unwrap())),
        );
        //let graphemes = UnicodeSegmentation::graphemes(&cap[3], true)
        //    .collect::<Vec<&str>>();
        //cell_pos += graphemes.len() as u16;
        cell_pos += cap[3].width() as u16;
        pos = cr.end();
        lpos = pos;
        lcell_pos = cell_pos;
    }
    //info!("load_esc set3 x={} str={}", lcell_pos + off_x, &l[lpos..l.len()]);
    content.set_str(
        lcell_pos + off_x,
        row + off_y,
        &l[lpos..l.len()],
        Style::default(),
    );
    //info!("load_esc line width = {}", lcell_pos + l[lpos..l.len()].width() as u16);
    lcell_pos + l[lpos..l.len()].width() as u16
}
